package com.yixin.hospital.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yixin.hospital.common.ApiResult;
import com.yixin.hospital.common.BusinessException;
import com.yixin.hospital.entity.bo.UserDeptWorkAddressJobOffice;
import com.yixin.hospital.entity.domain.*;
import com.yixin.hospital.entity.dto.req.visit.VisitAddReqDTO;
import com.yixin.hospital.entity.dto.req.visit.VisitHerbalDrugReqDTO;
import com.yixin.hospital.entity.dto.req.visit.VisitPatientCheckReqDTO;
import com.yixin.hospital.entity.dto.req.visit.VisitSubPackageDrugReqDTO;
import com.yixin.hospital.entity.vo.DrugDepositoryDrugVo;
import com.yixin.hospital.enums.DrugSellMethodEnum;
import com.yixin.hospital.enums.DrugVisitStatusEnum;
import com.yixin.hospital.enums.GuidanceStatusEnum;
import com.yixin.hospital.enums.ResponseCodeEnum;
import com.yixin.hospital.mapper.VisitMapper;
import com.yixin.hospital.mapper.extend.DrugDepositoryDrugExtendMapper;
import com.yixin.hospital.service.*;
import com.yixin.hospital.utils.CaffeineUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;

import static com.yixin.hospital.common.Constant.USER_CACHE;

/**
* @author Administrator
* @description 针对表【visit(就诊表)】的数据库操作Service实现
* @createDate 2024-11-13 17:12:41
*/
@Service
public class VisitServiceImpl extends ServiceImpl<VisitMapper, Visit>
    implements VisitService{

    @Resource
    private VisitSubPackageDrugService visitSubPackageDrugService;
    @Resource
    private VisitSubPackageDrugRealReportService visitSubPackageDrugRealReportService;
    @Resource
    private VisitPackageDrugService visitPackageDrugService;
    @Resource
    private VisitPackageDrugRealReportService visitPackageDrugRealReportService;
    @Resource
    private VisitHerbalDrugService visitHerbalDrugService;
    @Resource
    private VisitPatientCheckService visitPatientCheckService;
    @Resource
    private GuidanceService guidanceService;
    @Resource
    private DrugDepositoryDrugService drugDepositoryDrugService;
    @Resource
    private DrugDepositoryDrugExtendMapper drugDepositoryDrugExtendMapper;
    @Resource
    private DrugDepositoryService drugDepositoryService;

    private final ConcurrentHashMap<Long, ReentrantLock> productLocks = new ConcurrentHashMap<>();


    @Override
    public ApiResult<?> add(VisitAddReqDTO visitAddReqDTO, String userName) {
        visitAddReqDTO.getVisit().setCreatedBy(userName);
        visitAddReqDTO.getVisit().setUpdatedBy(userName);
        // 更新状态为 就诊
        Guidance one = guidanceService.getOne(new LambdaQueryWrapper<Guidance>().eq(Guidance::getGuidanceCode, visitAddReqDTO.getVisit().getGuidanceCode()).eq(Guidance::getPatientId, visitAddReqDTO.getVisit().getPatientId()));
        one.setStatus(GuidanceStatusEnum.VISITING.getCode());
        one.setVisitTime(LocalDateTime.now());
        guidanceService.updateById(one);
        Visit lastVisit = this.getOne(new LambdaQueryWrapper<Visit>().isNotNull(Visit::getEndUsageTime).eq(Visit::getPatientId, visitAddReqDTO.getVisit().getPatientId()).orderByDesc(Visit::getCreatedTime).last("LIMIT 1"));
        visitAddReqDTO.getVisit().setStartUsageTime(Objects.nonNull(lastVisit) ? lastVisit.getEndUsageTime().plusDays(1) : LocalDateTime.now());
        return ApiResult.success(this.save(visitAddReqDTO.getVisit()));
    }

    @Override
    public ApiResult<?> updateStauts(VisitAddReqDTO visitAddReqDTO, String userName) {
        // 更新状态为 就诊
        Guidance one = guidanceService.getOne(new LambdaQueryWrapper<Guidance>().eq(Guidance::getGuidanceCode, visitAddReqDTO.getGuidanceCode()));
        if(GuidanceStatusEnum.WAITING.getCode().equals(one.getStatus())) {
            one.setStatus(GuidanceStatusEnum.VISITING.getCode());
            one.setVisitTime(LocalDateTime.now());
            one.setUpdatedBy(userName);
            guidanceService.updateById(one);
        }
        return ApiResult.success();
    }

    @Override
    public ApiResult<?> detailVisitDrug(String guidanceCode) {
        List<VisitSubPackageDrug> subPackageDrugList = visitSubPackageDrugService.list(new LambdaQueryWrapper<VisitSubPackageDrug>().eq(VisitSubPackageDrug::getGuidanceCode, guidanceCode));
        VisitSubPackageDrugRealReport subPackageDrugRealReport = visitSubPackageDrugRealReportService.getOne(new LambdaQueryWrapper<VisitSubPackageDrugRealReport>().eq(VisitSubPackageDrugRealReport::getGuidanceCode, guidanceCode));
        List<VisitPackageDrug> packageDrugList = visitPackageDrugService.list(new LambdaQueryWrapper<VisitPackageDrug>().eq(VisitPackageDrug::getGuidanceCode, guidanceCode));
        VisitPackageDrugRealReport packageDrugRealReport = visitPackageDrugRealReportService.getOne(new LambdaQueryWrapper<VisitPackageDrugRealReport>().eq(VisitPackageDrugRealReport::getGuidanceCode, guidanceCode));
        VisitSubPackageDrugReqDTO visitSubPackageDrugReqDTO = new VisitSubPackageDrugReqDTO();
        visitSubPackageDrugReqDTO.setVisitSubPackageDrugList(subPackageDrugList);
        visitSubPackageDrugReqDTO.setVisitSubPackageDrugRealReport(subPackageDrugRealReport);
        visitSubPackageDrugReqDTO.setVisitPackageDrugList(packageDrugList);
        visitSubPackageDrugReqDTO.setVisitPackageDrugRealReport(packageDrugRealReport);
        Visit visit = this.getOne(new LambdaQueryWrapper<Visit>().eq(Visit::getGuidanceCode, guidanceCode));
        if (Objects.nonNull(visit)) {
            visitSubPackageDrugReqDTO.setVisit(visit);
            visitSubPackageDrugReqDTO.setStartUsageTime(visit.getStartUsageTime());
            visitSubPackageDrugReqDTO.setEndUsageTime(visit.getEndUsageTime());
        }
        return ApiResult.success(visitSubPackageDrugReqDTO);
    }

    @Override
    public ApiResult<?> addVisitSubPackageDrug(VisitSubPackageDrugReqDTO visitSubPackageDrugReqDTO, Long userId, String userName) {
        List<UserDeptWorkAddressJobOffice> list = (List<UserDeptWorkAddressJobOffice>) CaffeineUtils.get(USER_CACHE);
        Optional<UserDeptWorkAddressJobOffice> first = list.stream().filter(item -> item.getUserId().compareTo(userId) == 0).findFirst();
        if (!first.isPresent()) throw new BusinessException(ResponseCodeEnum.TOKEN_EXP.getMsg());
        UserDeptWorkAddressJobOffice userDeptWorkAddressJobOffice = first.get();
        DrugDepository drugDepository = drugDepositoryService.getOne(new LambdaQueryWrapper<DrugDepository>().eq(DrugDepository::getDept, userDeptWorkAddressJobOffice.getDeptId()));
        if (Objects.isNull(drugDepository)) throw new BusinessException(ResponseCodeEnum.DEPOSITORY_NOT_FOUND.getMsg());
        // 删除分装未付款药品 (删除前要加库存)
        List<VisitSubPackageDrug> subPackagePendingList = visitSubPackageDrugService.list(new LambdaQueryWrapper<VisitSubPackageDrug>().eq(VisitSubPackageDrug::getGuidanceCode, visitSubPackageDrugReqDTO.getGuidanceCode()).eq(VisitSubPackageDrug::getStatus, DrugVisitStatusEnum.PENDING.getCode()));
        for (VisitSubPackageDrug visitSubPackageDrug : subPackagePendingList) {
            // 加库存
            this.calculateStockCore(drugDepository, visitSubPackageDrug.getCode(), visitSubPackageDrug.getProductTime(), visitSubPackageDrug.getExpiration(), visitSubPackageDrug.getRealQuantity(), true, DrugSellMethodEnum.SUB_PACKAGE.getCode());
        }
        visitSubPackageDrugService.remove(new LambdaQueryWrapper<VisitSubPackageDrug>().eq(VisitSubPackageDrug::getGuidanceCode, visitSubPackageDrugReqDTO.getGuidanceCode()).eq(VisitSubPackageDrug::getStatus, DrugVisitStatusEnum.PENDING.getCode()));
        for (VisitSubPackageDrug visitSubPackageDrug : visitSubPackageDrugReqDTO.getVisitSubPackageDrugList()) {
            visitSubPackageDrug.setVisitId(visitSubPackageDrugReqDTO.getVisitId());
            visitSubPackageDrug.setGuidanceCode(visitSubPackageDrugReqDTO.getGuidanceCode());
            visitSubPackageDrug.setCreatedBy(userName);
            visitSubPackageDrug.setUpdatedBy(userName);
        }
        // 分装药品
        List<VisitSubPackageDrug> pendingSubPackageDrugList = new ArrayList<>();
        if (!CollectionUtils.isEmpty(visitSubPackageDrugReqDTO.getVisitSubPackageDrugList())) {
            // 只保存未支付处方
            pendingSubPackageDrugList = visitSubPackageDrugReqDTO.getVisitSubPackageDrugList().stream().filter(subPackageDrug -> subPackageDrug.getStatus().equals(DrugVisitStatusEnum.PENDING.getCode())).collect(Collectors.toList());
            visitSubPackageDrugService.saveBatch(pendingSubPackageDrugList);
            for (VisitSubPackageDrug visitSubPackageDrug : pendingSubPackageDrugList) {
                // 减库存
                this.calculateStockCore(drugDepository, visitSubPackageDrug.getCode(), visitSubPackageDrug.getProductTime(), visitSubPackageDrug.getExpiration(), visitSubPackageDrug.getRealQuantity(), false, DrugSellMethodEnum.SUB_PACKAGE.getCode());
            }
        }
        // 分装药品计算表
        visitSubPackageDrugRealReportService.remove(new LambdaQueryWrapper<VisitSubPackageDrugRealReport>().eq(VisitSubPackageDrugRealReport::getGuidanceCode, visitSubPackageDrugReqDTO.getGuidanceCode()));
        visitSubPackageDrugReqDTO.getVisitSubPackageDrugRealReport().setCreatedBy(userName);
        visitSubPackageDrugReqDTO.getVisitSubPackageDrugRealReport().setUpdatedBy(userName);
        visitSubPackageDrugRealReportService.save(visitSubPackageDrugReqDTO.getVisitSubPackageDrugRealReport());
        // 删除整装未付款药品
        List<VisitPackageDrug> packagePendingList = visitPackageDrugService.list(new LambdaQueryWrapper<VisitPackageDrug>().eq(VisitPackageDrug::getGuidanceCode, visitSubPackageDrugReqDTO.getGuidanceCode()).eq(VisitPackageDrug::getStatus, DrugVisitStatusEnum.PENDING.getCode()));
        for (VisitPackageDrug visitPackageDrug : packagePendingList) {
            // 加库存
            this.calculateStockCore(drugDepository, visitPackageDrug.getCode(), visitPackageDrug.getProductTime(), visitPackageDrug.getExpiration(), visitPackageDrug.getRealQuantity(), true, DrugSellMethodEnum.PACKAGE.getCode());
        }
        visitPackageDrugService.remove(new LambdaQueryWrapper<VisitPackageDrug>().eq(VisitPackageDrug::getGuidanceCode, visitSubPackageDrugReqDTO.getGuidanceCode()).eq(VisitPackageDrug::getStatus, DrugVisitStatusEnum.PENDING.getCode()));
        for (VisitPackageDrug visitPackageDrug : visitSubPackageDrugReqDTO.getVisitPackageDrugList()) {
            visitPackageDrug.setVisitId(visitSubPackageDrugReqDTO.getVisitId());
            visitPackageDrug.setGuidanceCode(visitSubPackageDrugReqDTO.getGuidanceCode());
            visitPackageDrug.setCreatedBy(userName);
            visitPackageDrug.setUpdatedBy(userName);
        }
        // 整装药品
        List<VisitPackageDrug> pendingPackageDrugList = new ArrayList<>();
        if(!CollectionUtils.isEmpty(visitSubPackageDrugReqDTO.getVisitPackageDrugList())) {
            // 只保存未支付处方
            pendingPackageDrugList = visitSubPackageDrugReqDTO.getVisitPackageDrugList().stream().filter(packageDrug -> packageDrug.getStatus().equals(DrugVisitStatusEnum.PENDING.getCode())).collect(Collectors.toList());
            visitPackageDrugService.saveBatch(pendingPackageDrugList);
            for (VisitPackageDrug visitPackageDrug : pendingPackageDrugList) {
                // 减库存
                this.calculateStockCore(drugDepository, visitPackageDrug.getCode(), visitPackageDrug.getProductTime(), visitPackageDrug.getExpiration(), visitPackageDrug.getRealQuantity(), false, DrugSellMethodEnum.PACKAGE.getCode());
            }
        }
        // 整装药品计算表
        visitPackageDrugRealReportService.remove(new LambdaQueryWrapper<VisitPackageDrugRealReport>().eq(VisitPackageDrugRealReport::getGuidanceCode, visitSubPackageDrugReqDTO.getGuidanceCode()));
        visitSubPackageDrugReqDTO.getVisitPackageDrugRealReport().setCreatedBy(userName);
        visitSubPackageDrugReqDTO.getVisitPackageDrugRealReport().setUpdatedBy(userName);
        visitPackageDrugRealReportService.save(visitSubPackageDrugReqDTO.getVisitPackageDrugRealReport());
        // 就诊信息
        Visit visit = this.getOne(new LambdaQueryWrapper<Visit>().eq(Visit::getGuidanceCode, visitSubPackageDrugReqDTO.getGuidanceCode()));
        visit.setDrugMark(visitSubPackageDrugReqDTO.getDrugMark());
        visit.setStartUsageTime(visitSubPackageDrugReqDTO.getStartUsageTime());
        visit.setEndUsageTime(visitSubPackageDrugReqDTO.getEndUsageTime());
        visit.setPlanUsageDays(visitSubPackageDrugReqDTO.getPlanUsageDays());
        this.updateById(visit);
        // 修改主记录状态(没有添加新药品，主记录状态不变)
        if (CollectionUtils.isEmpty(pendingPackageDrugList) && CollectionUtils.isEmpty(pendingSubPackageDrugList)) {
            return ApiResult.success();
        }
        Guidance guidance = guidanceService.getOne(new LambdaQueryWrapper<Guidance>().eq(Guidance::getGuidanceCode, visitSubPackageDrugReqDTO.getGuidanceCode()));
        guidance.setStatus(GuidanceStatusEnum.PAYING.getCode());
        guidanceService.updateById(guidance);
        return ApiResult.success();
    }

    @Override
    public ApiResult<?> addVisitHerbalDrug(VisitHerbalDrugReqDTO visitHerbalDrugReqDTO, String userName) {
        for (VisitHerbalDrug visitHerbalDrug : visitHerbalDrugReqDTO.getVisitHerbalDrugList()) {
            visitHerbalDrug.setVisitId(visitHerbalDrugReqDTO.getVisitId());
            visitHerbalDrug.setCreatedBy(userName);
            visitHerbalDrug.setUpdatedBy(userName);
        }
        visitHerbalDrugService.saveBatch(visitHerbalDrugReqDTO.getVisitHerbalDrugList());
        return ApiResult.success();
    }

    @Override
    public ApiResult<?> addVisitPatientCheck(VisitPatientCheckReqDTO visitPatientCheckReqDTO, String userName) {
        for (VisitPatientCheck visitPatientCheck : visitPatientCheckReqDTO.getVisitPatientCheckList()) {
            visitPatientCheck.setVisitId(visitPatientCheckReqDTO.getVisitId());
            visitPatientCheck.setCreatedBy(userName);
            visitPatientCheck.setUpdatedBy(userName);
        }
        visitPatientCheckService.saveBatch(visitPatientCheckReqDTO.getVisitPatientCheckList());
        return ApiResult.success();
    }

    private void calculateStockCore(DrugDepository drugDepository, String drugCode, Date productTime, Date expiration, Integer realQuantity, boolean isAddStore, Integer drugSellMethod) {
        // 查库存数据id  (库id、药品code、生产日期、过期时间)
        DrugDepositoryDrugVo drugDepositoryDrug = drugDepositoryDrugExtendMapper.getOneInventory(drugDepository.getId(), drugCode, productTime, expiration);
        // 锁库存表id
        ReentrantLock lock = new ReentrantLock();
        ReentrantLock existingLock = productLocks.putIfAbsent(drugDepositoryDrug.getId(), lock);
        lock = (existingLock == null) ? lock : existingLock;
        lock.lock();
        // 锁上之后再查一次库存(真实库存)
        drugDepositoryDrug = drugDepositoryDrugExtendMapper.getOneInventory(drugDepository.getId(), drugCode, productTime, expiration);
        try {
            // 减库存
            Map<String, Integer> stringIntegerMap = this.calculateInventory(drugDepositoryDrug, realQuantity, isAddStore, drugSellMethod);
            int i = drugDepositoryDrugExtendMapper.updateInventory(drugDepositoryDrug.getId(), drugDepositoryDrug.getNum(), drugDepositoryDrug.getSubNum(), stringIntegerMap.get("num"), stringIntegerMap.get("subNum"));
            if (i != 1) {
                throw new BusinessException(ResponseCodeEnum.INTERNAL_SERVER_ERROR.getMsg());
            }
        } finally {
            lock.unlock();
            // 可以在适当的时候清理不用的锁
            if (lock.getQueueLength() == 0) {
                productLocks.remove(drugDepositoryDrug.getId(), lock);
            }
        }
    }

    private Map<String, Integer> calculateInventory(DrugDepositoryDrugVo drugDepositoryDrug, Integer quantity, boolean isAddStore, Integer drugSellMethod) {
        Integer innerNum = drugDepositoryDrug.getInnerNum();
        int stockInventory = DrugSellMethodEnum.SUB_PACKAGE.getCode().equals(drugSellMethod) ? drugDepositoryDrug.getNum() * innerNum + drugDepositoryDrug.getSubNum() : drugDepositoryDrug.getNum();
        if (stockInventory < quantity) {
            // 库存不足 报错
            throw new BusinessException(String.format(ResponseCodeEnum.INVENTORY_INSUFFICIENT.getMsg(), drugDepositoryDrug.getDrugCode() + " [" + drugDepositoryDrug.getDrugCommonName() + "] " + drugDepositoryDrug.getDrugName()));
        }
        Map<String, Integer> numResult = new HashMap<>();
        // 分装剩余正好够本次扣减
        int reduceSubNum = isAddStore ? stockInventory + quantity : stockInventory - quantity;
        if (DrugSellMethodEnum.SUB_PACKAGE.getCode().equals(drugSellMethod)) {
            // 分装计算
            numResult.put("num", reduceSubNum / innerNum);
            numResult.put("subNum", reduceSubNum % innerNum);
        } else {
            // 整装计算
            numResult.put("num", reduceSubNum);
            numResult.put("subNum", drugDepositoryDrug.getSubNum());
        }
        // 如果库存为0，可以移除锁对象
        if (stockInventory - quantity == 0) {
            productLocks.remove(drugDepositoryDrug.getId());
        }
        return numResult;
    }



}




